<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>conway</title>
    <style>
      body,
      html {
        margin: 0px;
        overflow: hidden;
      }
      .button {
        border: none;
        color: white;
        padding: 15px 32px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 16px;
        margin: 4px 2px;
        cursor: pointer;
      }

      canvas {
        padding: 0;
        border: 0;
        background-color: white;
        image-rendering: pixelated;
        image-rendering: crisp;
        aspect-ratio: 1 / 1;
        position: relative;
        order : 0;
      }

      .controls {
        order : 1;
        vertical-align: middle;
        display: inline;
        border: 1px solid;
        position: relative;
      }

      .outer {
        top:0;
        right:0;
        left:0;
        justify-content: center;
        object-fit: contain;
        display: flexbox;
        background: gold;
        flex-wrap: wrap;
        align-items: center;
        width: 100%;
        height: auto;
        box-sizing: border-box;
        max-width: 100vw;
        max-height: 100vh;
        border: 1px dashed;
      }
      }
    </style>
  </head>
  <body>
    <div class="outer">
      <canvas class="canvas"></canvas>

      <div class="controls">
        <span id="debugbar">init</span>

        <span id="progressbar">init</span>

        <button type="button" id="clickme" onclick="evolve();rend()">
          Evolve!
        </button>

        <button type="button" id="clickme2" onclick="start_eloop();">
          start Evolve!
        </button>

        <button type="button" id="clickme3" onclick="pause_eloop();">
          pause Evolve!
        </button>

        <button type="button" id="clickme63" onclick="do_undo();">undo!</button>

        <button type="button" id="clickme4" onclick="bigger();rend();">
          bigger!
        </button>

        <textarea></textarea>
        <div>
          export <button onclick="export_copy();">(click to copy)</button>
        </div>
      </div>
    </div>

    <script>
      const dbg = document.getElementById("debugbar");
      dbg.innerText = "unlife";

      const canvas = document.querySelector("canvas");
      const ctx = canvas.getContext("2d");

      M = 30;
      N = 30;
      arr = new Uint8Array(M * N);
      arr2 = new Uint8Array(M * N);

      const evolve = () => {
        for (var y = 0; y < N; ++y) {
          for (var x = 0; x < M; ++x) {
            nei = [
              [-1, -1],
              [-1, 0],
              [-1, 1],
              [0, -1],
              [0, 1],
              [1, -1],
              [1, 0],
              [1, 1],
            ];
            arr2[M * y + x] = 0;
            nei.forEach(([a, b]) => {
              arr2[M * y + x] += arr[M * ((N + y + a) % N) + ((M + x + b) % M)];
            });
            arr2[M * y + x] = arr[M * y + x]
              ? arr2[M * y + x] == 3 || arr2[M * y + x] == 2
              : arr2[M * y + x] == 3;
          }
        }
        arr = [arr2, (arr2 = arr)][0];
      };

      const observer = new ResizeObserver(() => {
        canvas.width = canvas.parentElement.clientWidth;
        canvas.height = canvas.parentElement.clientHeight;
        mx = Math.min(canvas.height, canvas.width);
        canvas.width = mx;
        canvas.height = mx;
        rend();
      });

      observer.observe(canvas);

      var drawing = false;
      var positive = false;
      var mousePos = { x: 0, y: 0 };
      var lastPos = mousePos;

      const tc = (sx, sy) => {
        pos = getMousePos(canvas, sx, sy);
        lx = Math.floor(((pos.x - canvas.clientLeft) / canvas.clientWidth) * M);
        ly = Math.floor(((pos.y - canvas.clientTop) / canvas.clientHeight) * N);
        return [lx, ly];
      };

      const setAt = (xy) => {
        arr[xy[0] + M * xy[1]] |= 1;
      };

      const clearAt = (xy) => {
        arr[xy[0] + M * xy[1]] &= 0xfe;
      };

      const isOnAt = (xy) => {
        return arr[xy[0] + M * xy[1]] & 1;
      };

      const onTouchStart = (ev) => {
        if (event.targetTouches !== undefined) {
          x = event.targetTouches[0].clientX;
          y = event.targetTouches[0].clientY;
        } else {
          x = event.clientX;
          y = event.clientY;
        }
        ev.preventDefault();

        let xy = tc(x, y);

        if (isOnAt(xy)) {
          clearAt(xy);
          positive = false;
        } else {
          setAt(xy);
          positive = true;
        }

        drawing = true;
      };

      const onMoveDraw = (ev) => {
        let xy = tc(ev.x, ev.y);
        if (positive) {
          setAt(xy);
        } else {
          clearAt(xy);
        }
        rend();
      };

      const onTouchEnd = (ev) => {
        drawing = false;
        rend();
      };

      canvas.addEventListener(
        "touchstart",
        function (e) {
          onTouchStart(e);
        },
        false
      );

      canvas.addEventListener(
        "touchend",
        function (e) {
          onTouchEnd(e);
        },
        false
      );

      canvas.addEventListener(
        "mousedown",
        function (e) {
          onTouchStart(e);
        },
        false
      );

      canvas.addEventListener(
        "mouseup",
        function (e) {
          onTouchEnd(e);
        },
        false
      );

      canvas.addEventListener(
        "mousemove",
        function (e) {
          if (drawing) {
            onMoveDraw(e);
          }
        },
        false
      );

      function getMousePos(canva, cx, cy) {
        var rect = canva.getBoundingClientRect();
        return {
          x: cx - rect.left,
          y: cy - rect.top,
        };
      }

      const rend = () => {
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.lineWidth = 0.5;
        ctx.strokeStyle = "black";

        ys = canvas.height / N;
        xs = canvas.width / M;

        for (var x = 0; x < M; ++x) {
          if (M <= 60) {
            ctx.beginPath();
            ctx.moveTo(x * xs, 0);
            ctx.lineTo(x * xs, canvas.height);
            ctx.stroke();
          }
        }
        for (var y = 0; y < N; ++y) {
          if (N <= 60) {
            ctx.beginPath();
            ctx.moveTo(0, y * ys);
            ctx.lineTo(canvas.width, y * ys);
            ctx.stroke();
          }

          for (var x = 0; x < M; ++x) {
            if (arr[x + M * y] % 2) {
              ctx.fillStyle = "blue";
              ctx.fillRect(x * xs, y * ys, xs, ys);
            }
          }
        }
      };

      rend();

      running = false;

      const export_copy = () => {
        onCells = [];
        for (var y = 0; y < N; ++y) {
          for (var x = 0; x < M; ++x) {
            if (arr[x + M * y] % 2) {
              onCells.push([x, y]);
            }
          }
        }

        let txt = JSON.stringify({ onCells });
        dbg.innerText = txt;
        navigator.clipboard.writeText(txt);
      };

      const start_eloop = () => {
        if (!running) {
          intvl = setInterval(() => {
            evolve();
            rend();
          }, 200);
          running = true;
        }
      };

      const pause_eloop = () => {
        running = false;
        clearInterval(intvl);
      };

      const bigger = () => {
        Mold = M;
        Nold = N;
        M *= 2;
        N *= 2;

        arrn = new Uint8Array(M * N);
        arr2 = new Uint8Array(M * N);

        for (var y = 0; y < Nold; ++y) {
          for (var x = 0; x < Mold; ++x) {
            arrn[(Math.floor(Nold / 2) + y) * M + Math.floor(Mold / 2) + x] =
              arr[y * Mold + x];
          }
        }
        arr = arrn;
      };
    </script>
  </body>
</html>
